//=============================================================================
//
//   Copyright (C) 2020 All rights reserved.
//
//   Filename:		mod.rs
//
//   Author: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Date: 2020-10-09 12:46
//
//   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Last modified:	2020-11-19 13:37
//
//   Description:
//
//=============================================================================
// pub mod segment;
// pub mod vertex;

// use crate::component::mesh_component::{segment::Segment, vertex::Vertex};
use crate::dimension::DimType;
// use crate::geometry::point::Point;
// use crate::geometry::{integratable::Integratable, shape::ShapeLike};
// use crate::material::HasMaterial;
// use std::collections::HashMap;
// use std::rc::Weak;

mod vertex {
    use crate::component::{dof::Dof, gauss_point::GaussPoint, mesh_component::MeshComponent};
    use crate::dimension::DimType;
    use std::collections::HashMap;
    use std::rc::Weak;

    #[derive(Debug, serde::Deserialize)]
    pub struct Vertex<DT: DimType> {
        id: Option<usize>,
        #[serde(rename = "nodes")]
        pt_list: [usize; 1],
        #[serde(skip)]
        gp_list: Vec<GaussPoint<DT>>,
        mat: Option<usize>,
        #[serde(skip)]
        dof_list: HashMap<String, Dof>,
        #[serde(skip)]
        mesh: [HashMap<String, Weak<MeshComponent<DT>>>; 4],
    }
}

macro_rules! mesh_component_impl1 {
    (   $($dim:expr; $mod:ident; ($($enum:ident;($($struct:ident:$nn:expr);*));*));*   ) => {
        $(
            mod $mod{
                use crate::dimension::DimType;
                use std::collections::HashMap;
                use crate::component::{gauss_point::GaussPoint, mesh_component::MeshComponent};
                use crate::dimension::HasDimension;
                use std::rc::Weak;
                use nalgebra::Dim;
                $(
                    $(
                        #[derive(Debug, serde::Deserialize)]
                        pub struct $struct<DT:DimType>{
                            id:Option<usize>,
                            #[serde(rename = "nodes")]
                            pt_list: [usize; $nn],
                            #[serde(skip)]
                            gp_list: Vec<GaussPoint<DT>>,
                            mat: Option<usize>,
                            #[serde(skip)]
                            mesh: [HashMap<String, Weak<MeshComponent<DT>>>; 4],
                        }
                        impl<DT:DimType> $struct<DT>{
                            fn from_plist(pt_list:[usize;$nn]) -> Self {
                                Self {
                                    id: None,
                                    pt_list,
                                    gp_list: Vec::new(),
                                    mat: None,
                                    mesh: [
                                        HashMap::new(),
                                        HashMap::new(),
                                        HashMap::new(),
                                        HashMap::new(),
                                    ],
                                }
                            }
                        }
                        impl<DT: DimType> HasDimension for $struct<DT> {
                            fn get_topo_dim(&self) -> usize {
                                $dim as usize
                            }
                            fn get_dim(&self) -> usize {
                                DT::Dim::try_to_usize().expect("Failed to usize dimension")
                            }
                        }
                    )*
                    #[derive(Debug, serde::Deserialize)]
                    #[serde(untagged)]
                    pub enum $enum<DT:DimType>{
                        $($struct($struct<DT>),)*
                    }
                )*
            }
        )*
        #[derive(Debug, serde::Deserialize)]
        #[serde(tag = "type", rename_all = "lowercase")]
        pub enum MeshComponent<DT: DimType> {
            Vertex(vertex::Vertex<DT>),
            $(
                $(
                    $enum($mod::$enum<DT>),
                )*
            )*
        }
    };
}

mesh_component_impl1!(
    1; segment; (Segment;       (SegmentLinear: 2;
                                 SegmentQuad: 3));

    2; planar;  (Triangle;      (TriangleLinear: 3;
                                 TriangleQuad: 6);
                 Quadrangle;    (QuadrangleLinear: 4;
                                 QuadrangleQuad: 8));

    3; cubic;   (Tetrahedral;   (TetrahedralLinear: 4;
                                 TetrahedralQuad: 10);
                 TriPrism;      (TriPrismLinear: 6;
                                 TriPrismQuad: 15);
                 Octahedral;    (OctahedralLinear: 8;
                                 OctahedralQuad: 20))
);

// pub trait MeshComponentLike<DT: DimType>:
//     std::fmt::Debug + HasMaterial<DT> + Integratable<DT> + ShapeLike<DT>
// {
//     /// constructor with point list
//     fn from_plist(pt_list: Vec<Weak<Point<DT::Dim>>>) -> Self
//     where
//         Self: Sized;

//     fn get_connectivity(&self) -> &[HashMap<String, Weak<dyn MeshComponentLike<DT>>>; 4];
//     fn get_connectivity_mut(
//         &mut self,
//     ) -> &mut [HashMap<String, Weak<dyn MeshComponentLike<DT>>>; 4];

//     // -------------------------------------

//     fn get_std_n_by_local();

//     // -------------------------------------
//     fn append_neighbour(&mut self, rhs: Weak<dyn MeshComponentLike<DT>>) {
//         let shp = rhs
//             .upgrade()
//             .expect("Failed to upgrade weak pointer to neighbour shape");
//         let dim = shp.get_topo_dim();
//         let key = shp.get_hashkey();
//         self.get_connectivity_mut()
//             .get_mut(dim)
//             .expect("Failed to get mutable shapes")
//             .entry(key)
//             .or_insert(rhs);
//     }
//     fn get_neighbour_num(&self, dim: usize) -> usize {
//         self.get_connectivity()
//             .get(dim)
//             .expect("Failed to get shapes")
//             .len()
//     }
//     fn has_neighbour(&self, rhs: Weak<dyn MeshComponentLike<DT>>) -> bool {
//         let shp = rhs
//             .upgrade()
//             .expect("Failed to upgrade weak pointer to neighbour shape");
//         let dim = shp.get_topo_dim();
//         let key = shp.get_hashkey();
//         self.get_connectivity()
//             .get(dim)
//             .expect("Failed to get shapes")
//             .contains_key(&key)
//     }
// }

#[cfg(test)]
mod element_tests {
    use super::*;
    use crate::component::gauss_point::GaussPoint;
    use crate::dimension::{HasDimension, PlaneStrain};
    use crate::geometry::integratable::{get_weight, integrate, weighted_average};
    use crate::util::Hashable;
    use crate::Real;
    use nalgebra::dimension::Dim;
    use std::rc::Rc;

    #[test]
    fn serde() {
        let data =
            r#"{ "id": 0, "type": "quadrangle", "mat": 1, "nodes": [0, 6, 11, 9] } "#.to_string();
        let obj1: MeshComponent<PlaneStrain> = serde_json::from_str(&data).unwrap();
        println!("{:?}", obj1);
    }
    // #[derive(Debug)]
    // pub struct SomeElement<DT: DimType> {
    //     id: i32,
    //     pt_list: Vec<Weak<Point<DT::Dim>>>,
    //     hash_key: String,
    //     gp_list: Vec<GaussPoint<DT>>,
    //     mat: Option<Weak<dyn MaterialLike<DT>>>,
    //     mesh: [HashMap<String, Weak<dyn MeshComponentLike<DT>>>; 4],
    // }
    // impl<DT: DimType> Default for SomeElement<DT> {
    //     fn default() -> Self {
    //         Self {
    //             id: -1 as i32,
    //             pt_list: Vec::new(),
    //             hash_key: String::new(),
    //             gp_list: Vec::new(),
    //             mat: None,
    //             mesh: [
    //                 HashMap::new(),
    //                 HashMap::new(),
    //                 HashMap::new(),
    //                 HashMap::new(),
    //             ],
    //         }
    //     }
    // }
    // impl<DT: DimType> HasDimension for SomeElement<DT> {
    //     fn get_dim(&self) -> usize {
    //         2 as usize
    //     }
    //     fn get_topo_dim(&self) -> usize {
    //         DT::Dim::try_to_usize().expect("Failed to usize dimension")
    //     }
    // }
    // element_like_impl!(SomeElement<DT: DimType>);

    // #[test]
    // fn base() {
    // let mut p1 = Point::<<PlaneStrain as DimType>::Dim>::new(0.2f32, 0.3f32);
    // p1.set_index(1);
    // let mut p2 = Point::<<PlaneStrain as DimType>::Dim>::new(1f32, 0f32);
    // p2.set_index(8);
    // let mut p3 = Point::<<PlaneStrain as DimType>::Dim>::new(1f32, 1f32);
    // p3.set_index(7);
    // let mut p4 = Point::<<PlaneStrain as DimType>::Dim>::new(0f32, 1f32);
    // p4.set_index(3);

    // let sp1 = Rc::new(p1);
    // let sp2 = Rc::new(p2);
    // let sp3 = Rc::new(p3);
    // let sp4 = Rc::new(p4);

    // let mut elem1 = SomeElement::<PlaneStrain>::default();
    // assert_eq!(elem1.get_topo_dim(), 2);

    // elem1.append_point(Rc::downgrade(&sp1));
    // elem1.append_point(Rc::downgrade(&sp2));
    // elem1.append_point(Rc::downgrade(&sp3));
    // elem1.append_point(Rc::downgrade(&sp4));

    // assert_eq!(elem1.get_head_point().x(), 0.2f32);
    // assert_eq!(elem1.get_head_point().y(), 0.3f32);
    // assert_eq!(elem1.get_tail_point().x(), 0f32);
    // assert_eq!(elem1.get_tail_point().y(), 1f32);
    // assert_eq!(elem1.get_point(0).x(), 0.2f32);
    // assert_eq!(elem1.get_point(0).y(), 0.3f32);

    // let id_list = elem1.get_point_indices();
    // assert_eq!(id_list, vec![1, 8, 7, 3]);
    // assert_eq!("[1,3,7,8]", elem1.get_hashkey());

    // elem1.append_gauss_point(GaussPoint::<PlaneStrain>::new(0f32, 0f32, 1f32));
    // elem1.append_gauss_point(GaussPoint::<PlaneStrain>::new(1f32, 0f32, 1f32));
    // elem1.append_gauss_point(GaussPoint::<PlaneStrain>::new(1f32, 1f32, 1f32));
    // elem1.append_gauss_point(GaussPoint::<PlaneStrain>::new(0f32, 1f32, 1f32));
    // assert_eq!(get_weight(&elem1), 4f32);
    // assert_eq!(integrate(&elem1, |gp| gp.x() + gp.y()), 4f32);
    // assert_eq!(integrate(&elem1, |gp| gp.x()), 2f32);
    // assert_eq!(weighted_average(&elem1, |_| 1 as Real), 1f32);
    // }
}
